unit FormExplorer;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs, MandelbrotTiles, FMX.Objects,
  Interfaces, FMX.Ani, FMX.Menus, FMX.StdCtrls;

type
  TExplorerForm = class(TForm, IHostForm)
    trackZoom: TTrackBar;
    btnXZoomOut: TButton;
    btnZoomIn: TButton;
    rectZoomBackground: TRectangle;
    aniLoading: TAniIndicator;
    rectLoading: TRoundRect;
    lblLoading: TLabel;
    rectInfo: TRoundRect;
    lblInfoCoords: TLabel;
    lblInfoZoom: TLabel;
    lblTiles: TLabel;
    lblInfoCanvas: TLabel;
    tmrResizePurgeTiles: TTimer;
    pnlFractal: TPanel;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure pnlFractalResize(Sender: TObject);
    procedure trackZoomChange(Sender: TObject);
    procedure ZoomOutClick(Sender: TObject);
    procedure ZoomInClick(Sender: TObject);
    procedure pnlFractalMouseWheel(Sender: TObject; Shift: TShiftState; WheelDelta: Integer;
      var Handled: Boolean);
    procedure pnlFractalMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X,
      Y: Single);
    procedure pnlFractalMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X,
      Y: Single);
    procedure pnlFractalMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Single);
    procedure pnlFractalDblClick(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure tmrResizePurgeTilesTimer(Sender: TObject);
    procedure pnlFractalPaint(Sender: TObject; Canvas: TCanvas; const ARect: TRectF);
  private
    FTiles : TMandelbrotTiles;
    FBitmap : TBitmap;
    FDragging : Boolean;
    FLastDragPos : TPointF;
    procedure PurgeTilesDelay;
  public
    // IHostForm:
    procedure UpdateShowSpinner;
    procedure UpdateInformation;
    procedure TileAvailable;
    procedure AllTilesDownloaded;
    procedure RepaintUI;
    function GetIRoot : IRoot;
  end;

var
  ExplorerForm: TExplorerForm;

implementation

uses
  Math;

{$R *.fmx}

procedure TExplorerForm.FormCreate(Sender: TObject);
begin
  FBitmap := TBitmap.Create(Width, Height);
  FTiles := TMandelbrotTiles.Create(Self, FBitmap, ClientWidth, ClientHeight);

  pnlFractalResize(nil);
  FDragging := False;
end;

procedure TExplorerForm.FormDestroy(Sender: TObject);
begin
  FBitmap.Free;
  FTiles.Free;
end;

procedure TExplorerForm.pnlFractalPaint(Sender: TObject; Canvas: TCanvas; const ARect: TRectF);
var
  Rect : TRectF;
begin
  FTiles.DrawToUIBitmap;
  Rect := RectF(0, 0, FBitmap.Width, FBitmap.Height);
  pnlFractal.Canvas.DrawBitmap(FBitmap, Rect, Rect, 1.0);
end;

procedure TExplorerForm.pnlFractalDblClick(Sender: TObject);
begin
  ZoomInClick(Sender);
end;

procedure TExplorerForm.pnlFractalMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin
  FDragging := (Button = TMouseButton.mbLeft) or (Button = TMouseButton.mbMiddle);
  FLastDragPos := PointF(X, Y);
end;

procedure TExplorerForm.pnlFractalMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Single);
begin
  if FDragging then begin
    // Update dragging to the current position before stopping
    FTiles.ScrollByPixels(Round(X - FLastDragPos.X), Round(Y - FLastDragPos.Y));
    FTiles.UpdateVisibleTiles;
    PurgeTilesDelay;
    FLastDragPos := PointF(X, Y);
    Invalidate;
  end;
end;

procedure TExplorerForm.pnlFractalMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin
  if FDragging then begin
    // Update dragging to the current position before stopping
    FTiles.ScrollByPixels(Round(X - FLastDragPos.X), Round(Y - FLastDragPos.Y));
    FTiles.UpdateVisibleTiles;
    PurgeTilesDelay;
    Invalidate;
  end;

  FDragging := False;
end;

procedure TExplorerForm.pnlFractalMouseWheel(Sender: TObject; Shift: TShiftState;
  WheelDelta: Integer; var Handled: Boolean);
begin
  // In future, should zoom to keep the coordinates under the mouse stationary
  if WheelDelta < 0 then
    ZoomInClick(nil)
  else
    ZoomOutClick(nil);

  Handled := True;
end;

procedure TExplorerForm.pnlFractalResize(Sender: TObject);
begin
  FBitmap.Width := Round(pnlFractal.Width);
  FBitmap.Height := Round(pnlFractal.Height);

  FTiles.SetUISize(FBitmap.Width, FBitmap.Height);
  trackZoom.Min := FTiles.GetMinZoom;
  trackZoom.Max := FTiles.GetMaxZoom;
  FTiles.ZoomLevel := trackZoom.Value;
  FTiles.UpdateVisibleTiles;

  rectInfo.Position.X := 16;
  rectInfo.Position.Y := ClientHeight - 16 - rectInfo.Height; // No anchors in XE2; bottom-left

  Invalidate;
end;

procedure TExplorerForm.ZoomInClick(Sender: TObject);
begin
  trackZoom.Value := trackZoom.Value + 1;
end;

procedure TExplorerForm.ZoomOutClick(Sender: TObject);
begin
  trackZoom.Value := trackZoom.Value - 1;
end;

procedure TExplorerForm.trackZoomChange(Sender: TObject);
begin
  FTiles.SetZoomAni(trackZoom.Value);
  PurgeTilesDelay;
  Invalidate;
end;

procedure TExplorerForm.UpdateShowSpinner;
begin
  // IHostForm method. Called only in main thread
  rectLoading.Visible := Assigned(FTiles) and FTiles.IsWorking;
  rectLoading.Position.X := ClientWidth - 16 - rectLoading.Width;
  rectLoading.Position.Y := ClientHeight - 16 - rectLoading.Height; // No anchors in XE2; bottom-right
end;

procedure TExplorerForm.UpdateInformation;
var
  TileMemMB : Integer;
begin
  // IHostForm, main thread only.  Update the info display
  lblInfoCoords.Text := 'Coordinates: (' + FormatFloat('0.#####', FTiles.CoordX) + ', ' +
    FormatFloat('0.#####', FTiles.CoordY) + ')';
  lblInfoZoom.Text := 'Zoom: ' + FormatFloat('0.###', FTiles.ZoomLevel);
  TileMemMB := FTiles.TileMemoryBytes div (1024 * 1024); // Bytes to MB
  lblTiles.Text := 'Tiles: ' + IntToStr(FTiles.TileCount) + ' (approx ' + IntToStr(TileMemMB) + 'MB)';
  lblInfoCanvas.Text := 'Canvas: ' + FTiles.CanvasTypeName;
end;

procedure TExplorerForm.TileAvailable;
begin
  // IHostForm method. Called from main thread via Queue
  RepaintUI;
  UpdateInformation;
end;

procedure TExplorerForm.AllTilesDownloaded;
begin
  // IHostForm, called only in main thread (via Queue.) The thread downloaded everything it needed
  // to; purge unecessary tiles from memory
  UpdateShowSpinner; // Since probably no need to show it any more
  FTiles.PurgeOffscreenTiles;
end;

procedure TExplorerForm.RepaintUI;
begin
  // IHostForm.  Called, eg, when animating the zoom value
  Invalidate;
  UpdateShowSpinner;
end;

function TExplorerForm.GetIRoot: IRoot;
begin
  // For animations.  Main thread.
  Result := Self;
end;

procedure TExplorerForm.FormResize(Sender: TObject);
begin
  PurgeTilesDelay;
end;

procedure TExplorerForm.PurgeTilesDelay;
begin
  // Turn off (if already on) and then on again, to fire in a short time
  // Clearing tiles can take some time so don't do it immediately with every mouse movement / resize
  tmrResizePurgeTiles.Enabled := false;
  tmrResizePurgeTiles.Enabled := true;
end;

procedure TExplorerForm.tmrResizePurgeTilesTimer(Sender: TObject);
begin
  if FTiles.IsZooming then Exit; // Keep the animation as speedy as possible; timer will fire again soon

  tmrResizePurgeTiles.Enabled := false;

  // This can be slow; done only after a short delay when resizing
  FTiles.PurgeOffscreenTiles;
  FTiles.UpdateVisibleTiles;
end;



end.
